const fs = require('fs');
const path = require('path');
const crypto = require('crypto');


const directory = require('./directory');


const ts = require('typescript');

const tsCompileOptions = {
    sourceMap: false,
    noImplicitAny: true,
    module: 'ES6',
    target: 'es6',
    allowJs: true,
    jsx: 'preserve'
};


const transform = require('@babel/core').transform;

const jsCompileOptions = {
    presets: [
        [
            '@babel/preset-env', { 
                modules: false,
                targets: {
                    esmodules: true
                }
            }
        ]
    ],
    plugins: [
        // 必须放在syntax-decorators前
        require('./babel/plugin-property'),

        // 必须放在proposal-class-properties前
        ['@babel/plugin-syntax-decorators', { legacy: true }],
        '@babel/plugin-proposal-class-properties',

        // 必须要放在最后
        require('./babel/plugin-jsx.js')
    ]
};



function checkDirectory(publish, srcDirectoryPath, distDirectoryPath) {

    if (srcDirectoryPath !== false && !srcDirectoryPath) 
    {
        throw new Error('call method yaxi.webpack.platform error, publish.srcDirectoryPath can not be empty when publish ' + publish + '!');
    }

    if (distDirectoryPath !== false && !distDirectoryPath) 
    {
        throw new Error('call method yaxi.webpack.platform error, publish.distDirectoryPath can not be empty when publish ' + publish + '!');
    }
}



/**
 * 初始化平台
 * 
 * @param {String} platform 平台名 h5 || wx || web 必填
 * @param {Object} options 发布选项
 * @param {String} options.srcDirectoryPath src目录绝对路径 平台标准为"src"指向的目录
 * @param {String} options.distDirectoryPath dist目录绝对路径 平台标准为"dist"指向的目录
 * @param {Boolean} options.yaxi 是否发布yaxi库 默认为false
 * @param {Boolean} options.assets 是否发布assets资源 默认为false
 * @param {Boolean} options.js 是否发布js资源 默认为false
 * @param {String} options.mock 生成mock资源的路由路径 默认为空 为空时不生成mock
 */
function publish(platform, options) {

    if (!platform) 
    {
        throw new Error('call method yaxi.webpack.publish error, platform argument can not be empty!');
    }

    if ('h5|wx|web'.split('|').indexOf(platform) < 0) {
        throw new Error('call method yaxi.webpack.publish error, platform argument only supports h5 or wx or web!');
    }

    // 源目录
    let srcDirectoryPath = options.srcDirectoryPath;

    // 处理输出目录
    let distDirectoryPath = options.distDirectoryPath;

    // 发布yaxi库
    if (options.yaxi) 
    {
        checkDirectory('yaxi', false, distDirectoryPath);
        publish.yaxi(platform, distDirectoryPath);
    }

    // 发布js源码
    if (options.js) 
    {
        checkDirectory('js', srcDirectoryPath, distDirectoryPath);
        publish.js(path.join(srcDirectoryPath, 'js'), path.join(distDirectoryPath, 'js'));
    }

    // 发布assets资源
    if (options.assets) 
    {
        checkDirectory('assets', srcDirectoryPath, distDirectoryPath);
        publish.assets(path.join(srcDirectoryPath, 'assets'), path.join(distDirectoryPath, 'assets'));
    }

    // 发布mock数据
    if (options.mock) 
    {
        checkDirectory('mock', srcDirectoryPath, false);
        publish.mock(options.mock, path.join(srcDirectoryPath, 'mock'), false);
    }
}




/**
 * 不通过webpack打包直接发布yaxi库到指定目录
 * 
 * @param {String} platform 平台名 h5 || wx || web 必填
 * @param {String} distDirectoryPath 构建输出目录绝对路径 平台标准为"dist"指向的目录
*/
publish.yaxi = function (platform, distDirectoryPath) {

    let package = require('../package.json');

    package.main = 'dist/index.js';

    // 发布yaxi库
    distDirectoryPath = path.join(distDirectoryPath, 'node_modules/yaxi');
    directory.make(distDirectoryPath);

    // 写package.json文件
    fs.writeFileSync(path.join(distDirectoryPath, 'package.json'), JSON.stringify(package, null, 2), 'utf8');

    distDirectoryPath = path.join(distDirectoryPath, 'dist');
    directory.make(distDirectoryPath);

    // 复制js文件
    fs.copyFileSync(path.join(__dirname, '../dist/' + platform + '.js'), path.join(distDirectoryPath, 'index.js'));
}


/**
 * 直接发布js源码到指定的目录
 * 
 * @param {String} srcJSDirectoryPath js开发源码目录绝对路径 平台标准为"src/js"指向的目录
 * @param {String} distJSDirectoryPath js构建输出目录绝对路径 平台标准为"dist/{platform}/js"指向的目录
 */
publish.js = function publish(srcJSDirectoryPath, distJSDirectoryPath) {

    let names = fs.readdirSync(srcJSDirectoryPath);

    directory.make(distJSDirectoryPath);

    for (let i = 0, l = names.length; i < l; i++) 
    {
        let name = names[i];
        let source = path.join(srcJSDirectoryPath, name);
        let target = path.join(distJSDirectoryPath, name);

        if (fs.statSync(source).isFile()) 
        {
            if (/\.tsx?$/.test(name))
            {
                let text = fs.readFileSync(source, 'utf8');

                text = ts.transpileModule(text, tsCompileOptions).outputText;
                text = transform(text, jsCompileOptions).code;

                fs.writeFileSync(target.replace(/\.tsx?$/, '.js'), text, 'utf8');
            }
            else if (/\.jsx$/.test(name)) 
            {
                let text = fs.readFileSync(source, 'utf8');

                text = transform(text, jsCompileOptions).code;

                fs.writeFileSync(target.replace(/\.jsx$/, '.js'), text, 'utf8');
            }
            else
            {
                fs.copyFileSync(source, target);
            }
        }
        else
        {
            publish(source, target);
        }
    }
}


/**
 * 直接发布assets资源到指定的目录
 * 
 * @param {String} srcAssetsDirectoryPath assets开发源码目录绝对路径 平台标准为"src/assets"指向的目录
 * @param {String} distAssetsDirectoryPath assets构建输出目录绝对路径 平台标准为"dist/{platform}/assets"指向的目录
 * 
 * @returns {Object} 资源对应hash集合
*/
publish.assets = function (srcAssetsDirectoryPath, distAssetsDirectoryPath) {

    let outputs = {};
    let files = directory.find(srcAssetsDirectoryPath);
    let start = srcAssetsDirectoryPath.length;

    if (distAssetsDirectoryPath)
    {
        directory.copy(srcAssetsDirectoryPath, distAssetsDirectoryPath, /[\\/]\.\w+$/);
    }

    for (let i = 0, l = files.length; i < l; i++) 
    {
        let file = files[i];

        // 不处理资源文件
        if (file.endsWith('.assets.js'))
        {
            continue;
        }

        let data = fs.readFileSync(file);
        let hash = crypto.createHash('md5').update(data).digest('hex');

        file = file.substring(start).replace(/\\/g, '/');
        outputs[file] = hash.substring(0, 12);
    }

    fs.writeFileSync(path.join(srcAssetsDirectoryPath, '.assets.js'), 'module.exports = ' + JSON.stringify(outputs, null, 4), 'utf8');

    return outputs;
}



/**
 * 发布mock开发目录(文件变化后会自动更新路由)
 * 注1：此方法是为了实现一个简单的离线domo而把mock数据直接打包到目标平台上
 * 注2: 导出后的接口实现将受到限制, 不支持require, context.request, context.response
 * 
 * @param {String} path mock URL路径 
 * @param {String} mockDirectoryPath mock目录绝对路径 平台标准为"src/mock"指向的目录
 * @param {Boolean} session 是否需要session 可省略 默认值false
 */
publish.mock = function (path, mockDirectoryPath, session) {

    require('jiax').exportMockDirectory(path, mockDirectoryPath, session);
}



module.exports = publish;

